<?php
// This file is part of Preg question type - https://code.google.com/p/oasychev-moodle-plugins/
//
// Preg question type is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * This file is part of tReger
 * @package    qtype_preg
 * @copyright  2012 Oleg Sychev, Volgograd State Technical University
 * @author     Penskoy Nikita <nik95penik@yandex.ru>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();

global $CFG;
require_once($CFG->dirroot . '/question/type/poasquestion/poasquestion_string.php');
require_once($CFG->dirroot . '/question/type/preg/preg_unicode.php');
require_once($CFG->dirroot . '/question/type/preg/treger/build_nodes.php');


class Template {

    public $name;
    public $replacingname;
    public function __construct($name ='', $replacingname ='') {
        $this->name = new qtype_poasquestion_string($name);
        $this->replacingname = new qtype_poasquestion_string($replacingname);
    }
}

/**
 * Не заслэшен ли символ
 * \param [in] str - строка
 * \param [in] pos - позиция символа
 * \return - действует ли символ
 */
function not_slashed($str, $pos) {
    $slash = $pos > 1 && ($str[$pos - 1] != '\\' || ($str[$pos - 1] == '\\' && $str[$pos - 2] == '\\'));
    $slash = $slash || ($pos == 1 && $str[$pos - 1] != '\\');
    $slash = $slash || $pos === 0;
    return $slash;
}


/**
 * Проверка корректности описания
 * \param [in] str - описание
 * \param[in,out] out - поток для ошибок
 * \return - корректность описания
 */
function check_description($str) {
    $uni = new qtype_poasquestion_string($str);
    $openbrackets = array();    // Позиции незакрытых скобок.
    $out = '';
    $openquot = array();        // Позиция открывающей кавычки.
    $in = false;            // Сейчас в кавычках?

    $line = 1;
    for ($pos = 0; $pos < $uni->strlen($uni); $pos++) { // Для вех символов описания.
        if ( $uni[$pos] == '(' && !$in && not_slashed($uni, $pos)) {          // Если это открывающая скобка.
            $openbrackets[] = array($line, $pos);    // Добавим ее в список.
        } else if ($uni[$pos] == ')' && !$in && not_slashed($uni, $pos)) {     // Если это закрывающая скобка.
            if ( count($openbrackets) == 0) {    // Если список открывающих пуст.
                $out .= "incorrect close-bracket at line = ";
                $out .= $line;
                $out .= " pos = ";
                $out .= $pos + 1;
                $out .= "\n";
            } else {
                array_pop($openbrackets);  // Иначе удалим из списка открывающих скобок последнюю.
            }
        } else if (($uni[$pos] == '"' || $uni[$pos] == '«') && !$in && not_slashed($uni, $pos)) { // Если это открывающие кавычки.
            $in = true;
            $openquot[0] = $line;
            $openquot[1] = $pos; // Запомним их позицию.
        } else if (($uni[$pos] == '"' || $uni[$pos] == '»') &&
                $in && not_slashed($uni, $pos)) {    // Если это закрывающие кавычки после открывающих.
            $in = false;    // Запомним, что не в кавычках.
        } else if ($uni[$pos] == '»' && !$in && not_slashed($uni, $pos)) {    // Если не в кавычках.
                $out .= "incorrect close-quot at line = ";
                $out .= $line;
                $out .= " pos = ";
                $out .= $pos + 1;
                $out .= "\n";
        } else if ($uni[$pos] == "\n") {
            $line++;
        }
    }

    foreach ($openbrackets as $i) { // Для всех незакрытых скобок.
        $out .= "incorrect open-bracket at line = ";
        $out .= $i[0];
        $out .= " pos = ";
        $out .= $i[1] + 1;
        $out .= "\n";
    }
    if ($in) {    // Если были незакрытые открывающие кавычки.
        $out .= "incorrect open-quot at line = ";
        $out .= strval($openquot[0]);
        $out .= " pos = ";
        $out .= strval($openquot[1] + 1);
        $out .= "\n";
    }
    return $out;
}

/**
 * Разделитель ли символ
 * \param [in] i - символ
 * \return - разделитель или нет
 */
function is_sep($i) {

    return ($i == ' ' || $i == '.'  || $i == ','  || $i == '(' || $i == ')' || $i == '[' ||
            $i == ']' || $i == '\t' || $i == '\n' || $i == ':' || $i == ';' || $i == '?' ||
            $i == '!' || $i == '"'  || $i == '«'  || $i == '-');
}


/**
 * Заключение в кавычки всех, совпадающих с шаблонными, значений
 * \param [in,out] str - описание
 * \return - Возвращаемое значение отсутствует
 */
function set_all_state_patterns($str) {
    return preg_replace('/%%temp[0-9]+/', '«$0»', $str);
}

function sorting_templates($a, $b) {
    return $b->name->length() < $a->name->length();
}

/**
 * Проименовать шаблоны
 * \param [in,out] templates - список шаблонов
 * \return - Возвращаемое значение отсутствует
 */
function prepare_templates(&$templates) {
    $num = 0;
    $size = count($templates);
    for ($i = 0; $i < $size; $i++) {
        $templates[i]->replacingname->set_string("%%temp".$num); // Сгенерируем имя для замены шаблону
        $num++;
    }

    usort($templates, sorting_templates);
}

/**
 * Замена имен пользовательских шаблонов в описании на собственные
 * \param [in,out] str - описание
 * \param [in] templates - список шаблонов
 * \return - Возвращаемое значение отсутствует
 */
function mark_patterns_as_templates($str, &$templates) {
    $uni = new qtype_poasquestion_string($str);
    $quotes = array();
    $last = -1;
    $len = $uni->length();
    for ($i = 0; $i < $len; $i++) { // Для всех символов описания
        if (($uni[$i] == '"' || $uni[$i] == '«') && $last == -1 && not_slashed($uni, $i)) {   // Если не в кавычках и открывающая кавычка
            $quotes[$i] = -1;
            $last = $i;
            // Запомним, что в кавычках.
        } else if (($uni[$i] == '"' || $uni[$i] == '»') && $last != -1 && not_slashed($uni, $i)) { // Если в кавычках и закрывающая кавычка
            $quotes[$last] = $i;
            $last = -1;
        }
    }
    $templlen = count($templates);
    for ($i = 0; $i < $templlen; $i++) {
        $temp = $templates[$i];
        $pos = -1;
        // Просмотрим все вхождения шаблона.
        while ($pos < $uni->length() && ($pos = core_text::strpos($uni->string(), $temp->name->string(), $pos + 1)) !== false) {
            $inquotes = false;
            // Проверим, что не в кавычках.
            foreach ($quotes as $beg => $end) {
                if ($beg < $pos && $end > $pos) {
                    $inquotes = true;
                }
            }
            // Если это не часть слова, заменим шаблон.
            if (!$inquotes && $pos + $temp->name->length() - 1 < $uni->length() &&
                ($pos == 0 || is_sep($uni[$pos - 1])) &&
                ($pos + $temp->name->length() == $uni->length() ||
                is_sep($uni[$pos + $temp->name->length()]))) {
                $uni->set_string(core_text::substr($uni->string(), 0, $pos).$temp->replacingname->string().
                        core_text::substr($uni->string(), $pos + $temp->name->length()));
            }
        }
    }
    return $uni->string();
}

/**
 * Подготовка текста для парсинга 
 * \param [in,out] str - описание
 * \param [in,out] templates - список шаблонов
 * \return - Возвращаемое значение отсутствует
 */
function prepare_input_for_parsing(&$str, &$templates) {
    $templates = prepare_templates($templates);    // Проименуем шаблоны
    $str = set_all_state_patterns($str);    // Возьмем в кавычки все шаблонные значения в описании
    $str = mark_patterns_as_templates($str, $templates);    // Заменим все шаблоны на их имена
}

